﻿using Autofac;
using CrawlerApp.Core.Entities;
using CrawlerApp.Infrastructure.Data;
using CrawlerApp.Infrastructure.Interfaces;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;

namespace CrawlerApp.Infrastructure.Services
{
    public class Repository<T>: IRepository<T>
        where T : BaseEntity
    {
        //protected readonly ApplicationDbContext context;
        protected readonly ILifetimeScope lifetimeScope;

        public Repository(/*ApplicationDbContext context, */ILifetimeScope lifetimeScope)
        {
            //this.context = context;
            this.lifetimeScope = lifetimeScope;
        }
        public virtual async Task<T> GetByIdAsync(int id, bool onChange = false, CancellationToken cancellationToken = default)
        {
            using(var context = lifetimeScope.Resolve<ApplicationDbContext>())
            {
                IQueryable<T> query = context.Set<T>().AsQueryable();
                if (!onChange)
                    query = query.AsNoTracking();

                return await query.SingleOrDefaultAsync(it => it.Id == id, cancellationToken);
            }
        }
        public virtual async Task<T> FirstOrDefaultAsync(Expression<Func<T, bool>> predicate, bool onChange = false, CancellationToken cancellationToken = default)
        {
            using (var context = lifetimeScope.Resolve<ApplicationDbContext>())
            {
                IQueryable<T> query = context.Set<T>().AsQueryable();
                if (!onChange)
                    query = query.AsNoTracking();

                return await query.FirstOrDefaultAsync(predicate, cancellationToken);
            }
        }
        public virtual async Task<T> SingleOrDefaultAsync(Expression<Func<T, bool>> predicate = null, bool onChange = false, CancellationToken cancellationToken = default)
        {
            using (var context = lifetimeScope.Resolve<ApplicationDbContext>())
            {
                IQueryable<T> query = context.Set<T>().AsQueryable();
                if (!onChange)
                    query = query.AsNoTracking();

                return await query.SingleOrDefaultAsync(predicate, cancellationToken);
            }
        }
        public virtual async Task<IReadOnlyCollection<T>> ToListAsync(Expression<Func<T, bool>> whereExpression = null, bool onChange = false, CancellationToken cancellationToken = default)
        {
            using (var context = lifetimeScope.Resolve<ApplicationDbContext>())
            {
                IQueryable<T> query = context.Set<T>().AsQueryable();
                if (!onChange)
                    query = query.AsNoTracking();

                if (whereExpression != null)
                    query = query.Where(whereExpression);

                return await query.ToListAsync(cancellationToken);
            }
        }
        public virtual bool Exists(Expression<Func<T, bool>> predicate)
        {
            using (var context = lifetimeScope.Resolve<ApplicationDbContext>())
            {
                return context.Set<T>().AsNoTracking().Any(predicate);
            }
        }
        public virtual async Task<bool> ExistsAsync(Expression<Func<T, bool>> predicate, CancellationToken cancellationToken = default)
        {
            using (var context = lifetimeScope.Resolve<ApplicationDbContext>())
            {
                return await context.Set<T>().AsNoTracking().AnyAsync(predicate, cancellationToken);
            }
        }

        public virtual async Task<T> AddAsync(T entity, CancellationToken cancellationToken = default)
        {
            using (var context = lifetimeScope.Resolve<ApplicationDbContext>())
            {
                await context.Set<T>().AddAsync(entity, cancellationToken);
                await context.SaveChangesAsync(cancellationToken);

                return entity;
            }
        }

        public virtual async Task<IReadOnlyCollection<T>> AddRangeAsync(IReadOnlyCollection<T> entities, CancellationToken cancellationToken = default)
        {
            using (var context = lifetimeScope.Resolve<ApplicationDbContext>())
            {
                if (entities == null)
                    return entities;

                await context.Set<T>().AddRangeAsync(entities, cancellationToken);
                await context.SaveChangesAsync();

                return entities;
            }
        }

        public virtual async Task UpdateAsync(T entity, CancellationToken cancellationToken = default)
        {
            using (var context = lifetimeScope.Resolve<ApplicationDbContext>())
            {
                context.Entry(entity).State = EntityState.Modified;
                await context.SaveChangesAsync(cancellationToken);
            }
        }

        public virtual async Task DeleteAsync(T entity, CancellationToken cancellationToken = default)
        {
            using (var context = lifetimeScope.Resolve<ApplicationDbContext>())
            {
                context.Set<T>().Remove(entity);
                await context.SaveChangesAsync(cancellationToken);
            }
        }
    }
}
